<template>
  <CesiumBox ref="container"></CesiumBox>
</template>

<script setup>
import CesiumBox from "../../components/CesiumBox.vue";
import { ref, onMounted } from "vue";
import * as Cesium from "cesium";

const container = ref(null);
let cesiumPara = null;

class CircleScan {
  constructor(center, radius, ring, color, hz) {
    this.drawCommand = null;
    this.center = center;
    this.radius = radius;
    this.ring = ring;
    this.color = color;
    this.hz = hz;
  }

  setRadius(radius, ring) {
    this.radius = radius;
    this.ring = ring;
    this.circle = new Cesium.CircleGeometry({
      center: Cesium.Cartesian3.fromDegrees(this.center[0], this.center[1]),
      radius: Number(this.radius),
    });
    let geometry = Cesium.CircleGeometry.createGeometry(this.circle);

    let attributeLocations =
      Cesium.GeometryPipeline.createAttributeLocations(geometry);

    this.va = Cesium.VertexArray.fromGeometry({
      context: this.context,
      geometry: geometry,
      attributeLocations: attributeLocations,
    });

    this.createShader();

    this.shaderProgram = Cesium.ShaderProgram.fromCache({
      context: this.context,
      vertexShaderSource: this.vs,
      fragmentShaderSource: this.fs,
      attributeLocations: attributeLocations,
    });

    this.drawCommand.vertexArray = this.va;
    this.drawCommand.shaderProgram = this.shaderProgram;
  }

  setColor(color) {
    this.color = color;
    this.createUniformMap();
    this.drawCommand.uniformMap = this.uniformMap;
  }

  createShader() {
    this.vs = `
      attribute vec3 position;
      varying vec3 v_position;
      void main(){
        v_position = position;
        gl_Position = czm_projection * czm_modelView * vec4(position, 1.0);
      }
    `;

    this.fs = `
      uniform vec3 color;
      uniform vec3 center;
      uniform float time;
      varying vec3 v_position;
      void main(){
        float len = distance(v_position, center);
        float hz = len / time / ${this.radius};
        float ring = ${this.ring} / ${this.radius};
        if (hz > 1.0) {
          discard;
        }
        if (len < ${this.ring}) {
          discard;
        }
        if (ring != 0.0) {
          hz = (hz - ring) / ring;
        }
        vec4 res = mix(vec4(color,0), vec4(color,1), hz);
        gl_FragColor = res;
      }
    `;
  }

  createUniformMap() {
    let lng = this.center[0];
    let lat = this.center[1];
    let color = this.color;
    let hz = this.hz;
    let oldTime = new Date().getTime();
    this.uniformMap = {
      center() {
        let center = Cesium.Cartesian3.fromDegrees(lng, lat);
        return center;
      },
      color() {
        return color;
      },
      time() {
        let nowTime = ((new Date().getTime() - oldTime) % hz) / hz;
        return nowTime;
      },
    };
  }

  createCommand(context) {
    this.context = context;
    let circle = new Cesium.CircleGeometry({
      center: Cesium.Cartesian3.fromDegrees(this.center[0], this.center[1]),
      radius: Number(this.radius),
    });
    let geometry = Cesium.CircleGeometry.createGeometry(circle);

    let attributeLocations =
      Cesium.GeometryPipeline.createAttributeLocations(geometry);

    this.va = Cesium.VertexArray.fromGeometry({
      context: context,
      geometry: geometry,
      attributeLocations: attributeLocations,
    });

    this.createShader();
    this.shaderProgram = Cesium.ShaderProgram.fromCache({
      context: context,
      vertexShaderSource: this.vs,
      fragmentShaderSource: this.fs,
      attributeLocations: attributeLocations,
    });

    this.createUniformMap();

    let renderState = Cesium.RenderState.fromCache({
      cull: {
        enabled: true,
        face: Cesium.CullFace.BACK,
      },
      depthTest: {
        enabled: true,
      },
      blending: {
        enabled: true,
        //functionSourceRgb: Cesium.BlendFunction.SOURCE_COLOR,
        //functionSourceAlpha: Cesium.BlendFunction.SOURCE_ALPHA,
        functionDestinationRgb: Cesium.BlendFunction.SOURCE_ALPHA_SATURATE,
        //functionDestinationAlpha: Cesium.BlendFunction.DESTINATION_ALPHA,
      },
    });

    this.drawCommand = new Cesium.DrawCommand({
      vertexArray: this.va,
      shaderProgram: this.shaderProgram,
      uniformMap: this.uniformMap,
      renderState: renderState,
      pass: Cesium.Pass.OPAQUE,
    });
  }

  update(frameState) {
    if (!this.drawCommand) {
      this.createCommand(frameState.context);
    }
    frameState.commandList.push(this.drawCommand);
  }
}

function addCustomCircle() {
  Cesium.Material._materialCache.addMaterial("DynamicCircle1", {
    fabric: {
      type: "DynamicCircle1",
      uniforms: {
        color: { x: 1.0, y: 0.0, z: 0.0 },
        speed: 20,
      },
      source: `
        uniform vec3 color;
        uniform float speed;

        czm_material czm_getMaterial(czm_materialInput materialInput) {
          czm_material material = czm_getDefaultMaterial(materialInput);
          material.diffuse = color.rgb;
          vec2 st = materialInput.st;
          float dis = distance(st, vec2(0.5, 0.5));
          float per = fract(speed * czm_frameNumber / 1000.0);
          if (dis > per * 0.5) {
            material.alpha = 0.0;
          } else {
            material.alpha = dis / 0.5 / per;
          }
          return material;
        }
      `,
    },
    translucent: function () {
      return true;
    },
  });

  Cesium.Material._materialCache.addMaterial("DynamicCircle2", {
    fabric: {
      type: "DynamicCircle2",
      uniforms: {
        color: { x: 0.0, y: 1.0, z: 0.0 },
        speed: 15,
      },
      source: `
        uniform vec3 color;
        uniform float speed;

        czm_material czm_getMaterial(czm_materialInput materialInput) {
          czm_material material = czm_getDefaultMaterial(materialInput);
          material.diffuse = color.rgb;
          vec2 st = materialInput.st;
          float dis = distance(st, vec2(0.5, 0.5));
          float per = fract(speed * czm_frameNumber / 1000.0);
          if (dis < 0.25) {
            material.alpha = 0.0;
          } else if (dis > per * 0.5) {
            material.alpha = 0.0;
          } else {
            material.alpha = (dis - 0.25) / (per * 0.5 - 0.25);
          }
          return material;
        }
      `,
    },
    translucent: function () {
      return true;
    },
  });

  let appearance1 = new Cesium.MaterialAppearance({
    flat: true,
    material: new Cesium.Material({
      fabric: {
        type: "DynamicCircle1",
      },
    }),
    materialSupport: Cesium.MaterialAppearance.MaterialSupport.TEXTURED,
  });

  let appearance2 = new Cesium.MaterialAppearance({
    flat: true,
    material: new Cesium.Material({
      fabric: {
        type: "DynamicCircle2",
      },
    }),
    materialSupport: Cesium.MaterialAppearance.MaterialSupport.TEXTURED,
  });

  let primitive1 = new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.CircleGeometry({
        center: Cesium.Cartesian3.fromDegrees(124, 48),
        height: 0,
        radius: 250000.0,
        vertexFormat:
          Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat,
      }),
    }),
    appearance: appearance1,
    asynchronous: false,
  });
  cesiumPara.viewer.scene.primitives.add(primitive1);

  let primitive2 = new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
      geometry: new Cesium.CircleGeometry({
        center: Cesium.Cartesian3.fromDegrees(124, 48),
        height: 0,
        radius: 500000.0,
        vertexFormat:
          Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat,
      }),
    }),
    appearance: appearance2,
    asynchronous: false,
  });
  cesiumPara.viewer.scene.primitives.add(primitive2);
}

function addCircle() {
  addCustomCircle();

  let primitive1 = new CircleScan(
    [114, 44],
    "250000.0",
    "0.0",
    {
      x: 1.0,
      y: 0.0,
      z: 0.0,
    },
    2000
  );
  cesiumPara.viewer.scene.primitives.add(primitive1);

  let primitive2 = new CircleScan(
    [114, 44],
    "500000.0",
    "250000.0",
    {
      x: 0.0,
      y: 1.0,
      z: 0.0,
    },
    2000
  );
  cesiumPara.viewer.scene.primitives.add(primitive2);
}

onMounted(() => {
  cesiumPara = container.value.cesiumPara;
  addCircle();

  cesiumPara.viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(
      125.59719837773976,
      34.511151215544444,
      1438496.33035057
    ),
    orientation: {
      heading: 5.900144937823217,
      pitch: -0.8260025319357904,
      roll: 6.282441569785731,
    },
    duration: 3,
  });
});
</script>
